<?php

namespace tool;

class Chatgpt
{

    const FORM_URLENCODED = "application/x-www-form-urlencoded";
    /**
     * Generate a sorted query parameter string from parameter array.
     * such as array('b' => 1, 'a' => 2) to "a=1&b=2"
     *
     * @param array $paramArr
     * @return string sorted query string
     */
    function getSortedParameterStr(array $paramArr): string
    {
        ksort($paramArr);
        $tmpArr = array();
        foreach ($paramArr as $k => $v) {
            $tmp = $k;
            if (!empty($v)) {
                $tmp .= ("=" . $v);
            }
            $tmpArr[] = $tmp;
        }

        return join('&', $tmpArr);
    }

    /**
     * Send a HTTP request with App Authorization
     *
     * @param $apiAppKey string API App Key
     * @param $apiAppSecret string API App Secret
     * @param $method string HTTP Method of API
     * @param $url string Request URL of API, note that environment path (/release) is not allowed
     * @param $contentType string Request Content-Type header, set empty if request body is not needed
     * @param $acceptHeader string Accept HTTP request header
     * @param $reqBody string|null Request Body, set null if request body is not needed
     * @param $formParam array|null form parameters array, set null if not form request
     * @param $algorithm string|null Encryption algorithm: sha1, sha256, sha384, sha512, SM3, default to sha1
     * @param $customHeaders array|null Custom HTTP Headers, such as `array('x-header-a' => 1)`
     */
    function sendRequestWithAppAuth(string $apiAppKey, string $apiAppSecret, string $method, string $url, string $contentType, string $acceptHeader, string $reqBody=null, array $formParam=null, string $algorithm=null, array $customHeaders=null)
    {
        $contentMD5 = "";
        $isForm = ($contentType == self::FORM_URLENCODED);
        // Note: ContentMd5 is empty for application/x-www-form-urlencoded request
        if ($isForm) {
            assert(!is_null($formParam), "formParam is required for form request");
            // generate request body from form parameters
            $reqBody = $this->getSortedParameterStr($formParam);
        } elseif (!is_null($reqBody)) {
            // get content md5 for signing the request later
            $contentMD5 = base64_encode(md5($reqBody));
        }

        if (null === $algorithm) {
            $algorithm = "sha1";
        }

        // ===================================
        // STEP 1: Generate the string to sign
        // ===================================

        dump("1. URL:$url");

        // Note:
        // 1. parameters needs to be sorted in alphabetical order
        // 2. parameters include both query parameters and form parameters
        $paramArr = array();
        $parsedUrl = parse_url($url);
        if (!empty($parsedUrl['query'])) {
            parse_str($parsedUrl['query'], $paramArr);
        }
        if (!empty($formParam)) {
            $paramArr = array_merge($paramArr, $formParam);
        }

        $pathAndParam = $parsedUrl['path'];
        if (!empty($paramArr)) {
            $pathAndParam = $pathAndParam . '?' . $this->getSortedParameterStr($paramArr);
        }

        $xDateHeader = gmstrftime('%a, %d %b %Y %T %Z', time());
        $strToSign = sprintf("x-date: %s\n%s\n%s\n%s\n%s\n%s",
            $xDateHeader, $method, $acceptHeader, $contentType, $contentMD5, $pathAndParam);

        // Print stringToSign for debugging if authorization failed with 401
        $strToSignDebug = str_replace("\n", "#", $strToSign);
        dump("2. StringToSign:$strToSignDebug");

        // ===============================================================================
        // STEP 2: generate the signature (Authorization header) based on the stringToSign
        // ===============================================================================

        // Encode the string with HMAC and base64.
        $sign = base64_encode(hash_hmac($algorithm, $strToSign, $apiAppSecret, TRUE));
        $authHeader = sprintf(
            'hmac id="%s", algorithm="hmac-%s", headers="x-date", signature="%s"',
            $apiAppKey, $algorithm, $sign
        );

        $headers = array(
            'Host:' . $parsedUrl['host'],
            'Accept:' . $acceptHeader,
            'X-Date:' . $xDateHeader,
            'Authorization:' . $authHeader,
        );
        if (!empty($contentType)) {
            $headers[] = "Content-Type:" . $contentType;
        }
        if (!empty($contentMD5)) {
            $headers[] = "Content-MD5:" . $contentMD5;
        }
        if (is_array($customHeaders)) {
            foreach ($customHeaders as $k => $v) {
                $headers[] = $k . ":" . $v;
            }
        }

        dump("3. Request Headers:");
        dump($headers);

        // ============================
        // STEP 3: send the API request
        // ============================

        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 60);
        if (!empty($reqBody)) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $reqBody); // only required if request body is present
        }
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
        $data = curl_exec($ch);
        dd($data);
        if (curl_errno($ch)) {
            print "Error: " . curl_error($ch);
        } else {
            echo "Response: \n";
            var_dump($data);
            curl_close($ch);
        }
    }
}